﻿using MVCCentral.Framework.DesignByContract;
using MVCCentral.Framework.Infrastructure;
using MVCCentral.Framework.Infrastructure.Implementation;
using System.IO;
using System.Web;
using System.Web.Caching;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml;

namespace MVCCentral.Framework.Mvc.Menu
{
    public class MvcMenu : IMvcMenu
    {
        protected HtmlHelper Helper;

        protected string _ID;
        protected RouteValueDictionary _HtmlProperties;
        protected string _beforeText;
        protected string _afterText;
        protected string _xmlPath;
        protected bool _useSecurityTrimming = false;
        protected string _selectedClass = "selected";
        protected bool _selectedClassOnLi = false;
        protected string _SeparatorFirstLiClassNames;
        protected string _SeparatorFirstLiContent;
        protected string _SeparatorLiClassNames;
        protected string _SeparatorLiContent;
        protected string _SeparatorLastLiClassNames;
        protected string _SeparatorLastLiContent;
        protected bool _SuppressSelected;

        private XmlDocument XmlDoc;
        private HttpContextBase context;
        private ICacheManager cacheManager;

        public MvcMenu(HtmlHelper helper)
        {
            this.Helper = helper;
            this.context = helper.ViewContext.HttpContext;
            this.cacheManager = new CacheManagerWrapper();
        }


        private void Load()
        {
            XmlDoc = new XmlDocument();

            string content = File.ReadAllText(_xmlPath);

            if (!string.IsNullOrEmpty(content))
            {
                using (StringReader sr = new StringReader(content))
                {
                    using (XmlReader xr = XmlReader.Create(sr, new XmlReaderSettings { CloseInput = true, IgnoreWhitespace = true, IgnoreComments = true, IgnoreProcessingInstructions = true }))
                    {
                        XmlDoc.Load(xr);

                        InsertInCache(_xmlPath);
                    }
                }
            }
        }

        #region IMvcMenu Members

        /// <summary>
        /// Enter an ID for the UL element.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IMvcMenu ID(string id)
        {
            this._ID = id;
            return this;
        }

        /// <summary>
        /// Enter a dictionary of HtmlAttributes.
        /// </summary>
        /// <param name="dictionary"></param>
        /// <returns></returns>
        public IMvcMenu HtmlAttributes(object dictionary)
        {
            this._HtmlProperties = new RouteValueDictionary(dictionary);
            return this;
        }

        /// <summary>
        /// Set the XmlPath if different from ~/xml/menu.xml.
        /// </summary>
        /// <param name="xmlPath"></param>
        /// <returns></returns>
        public IMvcMenu XmlPath(string xmlPath)
        {
            Check.Require(!string.IsNullOrEmpty(xmlPath), "xmlPath is missing");
            string physicalPath = string.Empty;

#if DEBUG
            physicalPath = @"C:\VSProjects\MVCCentral.Framework\MVCCentral.Framework.UnitTests\bin\Debug\xml\mvccentralmenu.xml";
#else
            physicalPath = System.Web.Hosting.HostingEnvironment.MapPath(xmlPath);
#endif
            //if (string.IsNullOrEmpty(xmlPath))
            //    _xmlPath = XMLPATH;
            //else
            //_xmlPath = xmlPath;

            _xmlPath = physicalPath;

            return this;
        }

        /// <summary>
        /// Enter any CSS styles that are necessary before the item.
        /// </summary>
        /// <param name="beforeText"></param>
        /// <returns></returns>
        public IMvcMenu InjectBefore(string beforeText)
        {
            string str = beforeText ?? string.Empty;
            this._beforeText = str;
            return this;
        }

        /// <summary>
        /// Enter any CSS styles that are necessary after the item.
        /// </summary>
        /// <param name="afterText"></param>
        /// <returns></returns>
        public IMvcMenu InjectAfter(string afterText)
        {
            string str = afterText ?? string.Empty;
            this._afterText = str;
            return this;
        }

        /// <summary>
        /// Choose whether this menu uses security trimming. Default is false.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public IMvcMenu UseSecurityTrimming(bool value)
        {
            this._useSecurityTrimming = value;
            return this;
        }

        /// <summary>
        /// Set the name of the class for the selected link.
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public IMvcMenu SelectedClass(string className)
        {
            if (!string.IsNullOrEmpty(className))
                _selectedClass = className;
            return this;
        }

        /// <summary>
        /// Set this to True of the selected CSS class goes on the li element
        /// instead of the link.  The Link is the default.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public IMvcMenu SelectedClassOnLi(bool value)
        {
            _selectedClassOnLi = value;
            return this;
        }

        /// <summary>
        /// This creates a separate "li" in the "ul" for menus that require
        /// a leading "li" for formatting purposes.  Enter a list of classnames
        /// separated by commas, and any content, such as a pipe, '|'.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public IMvcMenu SeparatorFirstLi(string classNames, string content)
        {
            string tempClassString = string.Empty;
            string[] list = classNames.Split(new char[] { ',' });
            foreach (string item in list)
            {
                tempClassString += item + " ";
            }
            _SeparatorFirstLiContent = content;
            _SeparatorFirstLiClassNames = tempClassString.Trim();

            return this;
        }

        /// <summary>
        /// This creates an "li" between each menu "li".  You can enter
        /// a list of comma-delimited CSS classes and any content, such 
        /// as a pipe, '|'.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public IMvcMenu SeparatorLi(string classNames, string content)
        {
            string tempClassString = string.Empty;
            string[] list = classNames.Split(new char[] { ',' });
            foreach (string item in list)
            {
                tempClassString += item + " ";
            }

            _SeparatorLiClassNames = tempClassString.Trim();
            _SeparatorLiContent = content;

            return this;
        }

        /// <summary>
        /// This creates an ending "li" that is used for formatting.  You can
        /// enter a comma-delimited list of CSS classes and any content, such
        /// as a pipe, '|'.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public IMvcMenu SeparatorLastLi(string classNames, string content)
        {
            string tempClassString = string.Empty;
            string[] list = classNames.Split(new char[] { ',' });
            foreach (string item in list)
            {
                tempClassString += item + " ";
            }

            _SeparatorLastLiClassNames = tempClassString.Trim();
            _SeparatorLastLiContent = content;

            return this;
        }

        /// <summary>
        /// This selection will suppress or disable the highlighting of
        /// the selected navigation item.
        /// </summary>
        /// <returns></returns>
        public IMvcMenu SuppressSelected()
        {
            _SuppressSelected = true;
            return this;
        }

        /// <summary>
        /// Render the xml-based menu.
        /// </summary>
        /// <returns></returns>
        public void Render()
        {
            var route = Helper.ViewContext.RequestContext.RouteData;
            string area = null;
            if (route.DataTokens["area"] != null)
                area = route.DataTokens["area"].ToString();

            var controller = route.GetRequiredString("controller");
            var action = route.GetRequiredString("action");

            Load();

            var ulTag = new TagBuilder("ul");
            if (!string.IsNullOrEmpty(_ID))
            {
                ulTag.MergeAttribute("id", _ID ?? string.Empty);
            }
            ulTag.MergeAttributes(_HtmlProperties);

            var childNodes = XmlDoc.DocumentElement.ChildNodes;

            BuildMenuTags(area, controller, action, ulTag, childNodes);

#if DEBUG
            System.Console.WriteLine(ulTag);
#endif
            context.Response.Write(ulTag);
            //return null;
        }


        public string RenderString()
        {
            var route = Helper.ViewContext.RequestContext.RouteData;
            string area = null;
            if (route.DataTokens["area"] != null)
                area = route.DataTokens["area"].ToString();

            var controller = route.GetRequiredString("controller");
            var action = route.GetRequiredString("action");

            Load();

            var ulTag = new TagBuilder("ul");
            if (!string.IsNullOrEmpty(_ID))
            {
                ulTag.MergeAttribute("id", _ID ?? string.Empty);
            }
            ulTag.MergeAttributes(_HtmlProperties);

            var childNodes = XmlDoc.DocumentElement.ChildNodes;

            BuildMenuTags(area, controller, action, ulTag, childNodes);

            return ulTag.ToString();
        }

        private void BuildMenuTags(string controller, string action, TagBuilder ulTag, XmlNodeList childNodes)
        {
            InternalBuildMenuTags(null, controller, action, ulTag, childNodes);
        }

        private void BuildMenuTags(string area, string controller, string action, TagBuilder ulTag, XmlNodeList childNodes)
        {
            InternalBuildMenuTags(area, controller, action, ulTag, childNodes);
        }

        private void InternalBuildMenuTags(string area, string controller, string action, TagBuilder ulTag, XmlNodeList childNodes)
        {
            bool blnOk = true;
            string actionAttr = string.Empty;
            bool isFirstTime = true;

            if (!string.IsNullOrEmpty(_SeparatorFirstLiClassNames))
            {
                var liTag = new TagBuilder("li");
                liTag.InnerHtml = _SeparatorFirstLiContent;
                liTag.AddCssClass(_SeparatorFirstLiClassNames);
                ulTag.InnerHtml += liTag.ToString();
            }

            foreach (XmlElement element in childNodes)
            {
                //bool actionHasValue = false;
                if (_useSecurityTrimming)
                {
                    blnOk = IsInRole(element.Attributes["Roles"].Value);
                }

                if (!isFirstTime)
                {
                    if (!string.IsNullOrEmpty(_SeparatorLiClassNames))
                    {
                        var liTag = new TagBuilder("li");
                        liTag.InnerHtml = _SeparatorLiContent;
                        liTag.AddCssClass(_SeparatorLiClassNames);
                        ulTag.InnerHtml += liTag.ToString();
                    }
                }
                isFirstTime = false;

                if (blnOk)
                {
                    
                    string route = BuildRoute(element.Attributes["Area"].Value, element.Attributes["Controller"].Value, element.Attributes["Action"].Value);
                    string text = element.Attributes["Text"].Value;
                    text = _beforeText + text + _afterText;

                    var liTag = new TagBuilder("li");
                    var aTag = new TagBuilder("a");
                    aTag.Attributes["href"] = route;
                    aTag.InnerHtml = text;


                    //liTag.InnerHtml = "<a href=\"/" + element.Attributes["Controller"].Value;

                    //if (!string.IsNullOrEmpty(element.Attributes["Action"].Value))
                    //{
                    //    liTag.InnerHtml += "/" + element.Attributes["Action"].Value;
                    //    actionAttr = element.Attributes["Action"].Value;
                    //}
                    //else
                    //{
                    //    actionAttr = "Index";
                    //}

                    XmlAttribute param = element.Attributes["Parameters"];
                    if (param != null)
                        liTag.InnerHtml += "/" + element.Attributes["Parameters"].Value;

                    if (!_SuppressSelected)
                    {
                        if (controller == element.Attributes["Controller"].Value)
                        {
                            //if (!_selectedClassOnLi)
                            //    liTag.InnerHtml += "\" class=\"" + _selectedClass + "";
                            //else
                            //    liTag.AddCssClass(_selectedClass);

                            if (_selectedClassOnLi)
                                liTag.AddCssClass(_selectedClass);
                            else
                                aTag.AddCssClass(_selectedClass);
                        }
                    }

                    liTag.InnerHtml = aTag.ToString();

                    //liTag.InnerHtml += "\">" + _beforeText + element.Attributes["Text"].Value + _afterText + "</a>";

                    // Recursion goes here
                    //if (element.HasChildNodes)
                    //{
                    //    var childUlTag = new TagBuilder("ul");
                    //    foreach (XmlElement child in element.ChildNodes)
                    //    {
                    //        var childLiTag = new TagBuilder("li");
                    //        childLiTag.InnerHtml = "<a href=\"/" + child.Attributes["Controller"].Value;

                    //        if (!string.IsNullOrEmpty(child.Attributes["Action"].Value))
                    //            childLiTag.InnerHtml += "/" + child.Attributes["Action"].Value;

                    //        if (controller == child.Attributes["Controller"].Value && action.Contains(child.Attributes["Action"].Value))
                    //            childLiTag.InnerHtml += " class=\"" + _selectedClass + "";

                    //        childLiTag.InnerHtml += "\"><span><span>" + child.Attributes["Text"].Value + "</span></span></a>";

                    //        childUlTag.InnerHtml += childLiTag.ToString();
                    //    }
                    //    liTag.InnerHtml += childUlTag.ToString();
                    //}

                    ulTag.InnerHtml += liTag.ToString();
                }
            }
        }

        internal void InsertInCache(string filePath)
        {
            string cacheKey = GetType().AssemblyQualifiedName + ":" + filePath;

            if (cacheManager.GetItem(cacheKey) == null)
            {
                cacheManager.Insert(cacheKey, filePath, OnCacheItemRemoved, filePath);
            }
        }

        internal void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason)
        {
            if (reason == CacheItemRemovedReason.DependencyChanged)
            {
                _xmlPath = (string)value;
                Load();
            }
        }

        private string BuildRoute(string area, string controller, string action)
        {
            string appRoot = context.Request.ApplicationPath.TrimEnd('/');
            string route = string.Empty;

            if (!string.IsNullOrEmpty(appRoot))
            {
                route += appRoot;
            }

            if (!string.IsNullOrEmpty(area))
            {
                route += "/" + area;
            }

            if (!string.IsNullOrEmpty(controller))
            {
                route += "/" + controller;
            }

            if (!string.IsNullOrEmpty(action))
            {
                route += "/" + action;
            }
            return route;
        }



        private string UrlBuilder(string area, string controller, string action)
        {
            return string.Empty;
        }

        private bool IsInRole(string roleName)
        {
            bool result = false;
            string[] split = roleName.Split(',');

            foreach (string item in split)
            {
                if (context.User.IsInRole(item.Trim()))
                {
                    result = true;
                    break;
                }
            }

            return result;
        }

        #endregion



        #region IMvcMenu Members



        #endregion

    }

    public interface IMvcMenu
    {
        IMvcMenu ID(string id);
        IMvcMenu HtmlAttributes(object dictionary);
        IMvcMenu XmlPath(string xmlPath);
        IMvcMenu InjectBefore(string beforeString);
        IMvcMenu InjectAfter(string afterString);
        IMvcMenu UseSecurityTrimming(bool value);
        IMvcMenu SelectedClass(string className);
        IMvcMenu SelectedClassOnLi(bool value);
        /// <summary>
        /// Comma-delimited string of class names.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        IMvcMenu SeparatorFirstLi(string classNames, string content);

        /// <summary>
        /// Comma-delimited string of class names.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        IMvcMenu SeparatorLi(string classNames, string content);

        /// <summary>
        /// Comma-delimited string of class names.
        /// </summary>
        /// <param name="classNames"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        IMvcMenu SeparatorLastLi(string classNames, string content);
        void Render();
        string RenderString();
        IMvcMenu SuppressSelected();
    }

    public class MvcMenuItem
    {
        public string Text { get; set; }
        public string Action { get; set; }
        public string Controller { get; set; }

        public MvcMenuItem(string text, string action, string controller)
        {
            this.Text = text;
            this.Action = action;
            this.Controller = controller;
        }
    }
}
